package club.jdiy.core;

import club.jdiy.core.base.domain.ConfigBean;
import club.jdiy.core.base.domain.ConfigPrefix;
import club.jdiy.core.base.domain.Pager;
import club.jdiy.core.cache.CacheBuilder;
import club.jdiy.core.cache.CacheClient;
import club.jdiy.core.properties.JDiyProperties;
import club.jdiy.core.convert.Converter;
import club.jdiy.core.ex.JDiyConvertException;
import club.jdiy.core.sql.Args;
import club.jdiy.core.sql.Dao;
import club.jdiy.core.sql.Rs;
import club.jdiy.core.sql.SqlDao;
import club.jdiy.utils.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import javax.annotation.Resource;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * JDiy应用程序全局上下文(脱离HttpServletRequest作用域的基类).
 *
 * @author ziquee(子秋) QQ号:39886616 主页: http://www.jdiy.club
 * @version 20200731
 */
@SuppressWarnings("unused")
abstract class Context implements ApplicationContextAware {


    public File getUploadDir() {
        //判断以下4种配置方式：
        // 1. 若为空值, 则uploads目录默认和jar文件于处同级目录(生产环境)，或target/uploads(开发环境)
        // 2. 若为 file:/xxx/xxx 的形式，则uploads位于操作系统(绝对路径) /xxx/xxx
        // 3. 若为 classpath:/xxx/xxx 的形式，则uploads位于jar同级目录下的/xxx/xxx中，或者 target/xxx/xxx中
        // 4. 若为其它值，则直接当作操作系统绝对路径的形式用new File构建(即等同于 file:/xxx/xxx的形式)
        if (StringUtils.isEmpty(jDiyProperties.getUploadDir()) || jDiyProperties.getUploadDir().startsWith("classpath:")) {
            String p = applicationContext.getClassLoader().getResource("").getPath();
            int i = p.indexOf(".jar!");
            if (i != -1) {
                p = p.substring(0, i);
                p = p.substring(0, p.lastIndexOf("/"));
            }
            if (p.endsWith("/")) p = p.substring(0, p.length() - 1);
            return new File(p + "/uploads");
        } else if (jDiyProperties.getUploadDir().startsWith("file:")) {
            return new File(jDiyProperties.getUploadDir().substring(5).trim());
        } else {
            return new File(jDiyProperties.getUploadDir().trim());
        }
    }

    public File getStaticDir() {
        //判断以下4种配置方式：
        // 1. 若为空值, 则static目录默认和jar文件于处同级目录(生产环境)，或target/static(开发环境)
        // 2. 若为 file:/xxx/xxx 的形式，则static位于操作系统(绝对路径) /xxx/xxx
        // 3. 若为 classpath:/xxx/xxx 的形式，则static位于jar同级目录下的/xxx/xxx中，或者 target/xxx/xxx中
        // 4. 若为其它值，则直接当作操作系统绝对路径的形式用new File构建(即等同于 file:/xxx/xxx的形式)
        if (StringUtils.isEmpty(jDiyProperties.getStaticDir()) || jDiyProperties.getStaticDir().startsWith("classpath:")) {
            String p = Objects.requireNonNull(applicationContext.getClassLoader().getResource("")).getPath();
            int i = p.indexOf(".jar!");
            if (i != -1) {
                p = p.substring(0, i);
                p = p.substring(0, p.lastIndexOf("/"));
            }
            if (p.endsWith("/")) p = p.substring(0, p.length() - 1);
            return new File(p + "/static");
        } else if (jDiyProperties.getStaticDir().startsWith("file:")) {
            return new File(jDiyProperties.getStaticDir().substring(5).trim());
        } else {
            return new File(jDiyProperties.getStaticDir().trim());
        }
    }

    @SuppressWarnings("unchecked")
    public Map<String, String> getConfig() {
        Pager<Rs> ls = null;
        try {
            Object o = getCacheClient().get("CONFIG_CACHED");
            if (o instanceof Pager) ls = (Pager<Rs>) o;
        } catch (Exception ignore) {
        }
        if (ls == null) {
            ls = getDao().ls(new Args("jdiy_conf"));
            getCacheClient().set("CONFIG_CACHED", ls, 60 * 60);
        }
        return ls.getItems().stream().collect(Collectors.toMap(Rs::id, it -> it.getString("v"), (a, b) -> b));
    }

    @SuppressWarnings("unchecked")
    public <T extends ConfigBean> T getConfig(Class<T> c) {
        Pager<Rs> ls = null;
        Object o = getCacheClient().get("CONFIG_CACHED");
        if (o instanceof Pager) ls = (Pager<Rs>) o;
        if (ls == null) {
            ls = getDao().ls(new Args("jdiy_conf"));
            getCacheClient().set("CONFIG_CACHED", ls, 60 * 60);
        }

        T appConfig;
        try {
            appConfig = c.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new JDiyConvertException("目标class “" + c + "”没有默认的构造方法或访问受限，无法创建实例。");
        }
        String finalPrefix = getConfigPrefix(c);
        ls.getItems().forEach(it -> {
            String id = it.id();
            if (id != null && id.startsWith(finalPrefix)) {
                try {
                    String val = it.getString("v");
                    Converter conv = Converter.of(val);
                    Field field = c.getDeclaredField(id.substring(finalPrefix.length()));
                    if (Modifier.isFinal(field.getModifiers())) return;
                    field.setAccessible(true);
                    Class<?> type = field.getType();
                    if (int.class.equals(type) || Integer.class.equals(type)) {
                        field.set(appConfig, conv.getInteger());
                    } else if (Long.class.equals(type) || long.class.equals(type)) {
                        field.set(appConfig, conv.getLong());
                    } else if (Boolean.class.equals(type) || boolean.class.equals(type)) {
                        field.set(appConfig, conv.getBoolean());
                    } else if (Double.class.equals(type) || double.class.equals(type)) {
                        field.set(appConfig, conv.getDouble());
                    } else if (Date.class.equals(type)) {
                        field.set(appConfig, conv.getDate());
                    } else if (String.class.equals(type)) {
                        field.set(appConfig, val);
                    } else {
                        field.set(appConfig, conv.to(type));

                    }
                    field.setAccessible(false);
                } catch (NoSuchFieldException ignore) {
                } catch (ReflectiveOperationException le) {
                    le.printStackTrace();
                }
            }
        });
        return appConfig;
    }


    public void saveConfig(ConfigBean cb) {
        try {
            ConfigBean po = getConfig(cb.getClass());
            Field[] fields = cb.getClass().getDeclaredFields();

            String finalPrefix = getConfigPrefix(cb.getClass());
            for (Field f : fields) {
                if (Modifier.isFinal(f.getModifiers())) continue;
                Object v = null, v0 = null;
                try {
                    f.setAccessible(true);
                    v = f.get(cb);
                    v0 = f.get(po);
                    f.setAccessible(false);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                if (!Objects.equals(v, v0)) {
                    //先rs查询再set,防止rs没有数据时id无值
                    String id = finalPrefix + f.getName();

                    Rs rs = getDao().rs("jdiy_conf", id);
                    rs.set(rs.getPrimaryKey(), id);

                    if (v == null) getDao().del(rs);
                    else {
                        rs.set("v", Converter.of(v).getString());
                        getDao().save(rs);
                    }
                }
            }
        } finally {
            getCacheClient().delete("CONFIG_CACHED");
        }
    }

    public CacheClient getCacheClient() {
        return CacheBuilder.getClient(applicationContext, jDiyProperties);
    }

    public Dao getDao() {
        return sqlDao;
    }

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Context.applicationContext = applicationContext;
    }

    private <T extends ConfigBean> String getConfigPrefix(Class<T> c) {
        ConfigPrefix cp = c.getAnnotation(ConfigPrefix.class);
        String prefix = cp == null ? "" : cp.value().trim();
        if (!"".equals(prefix) && !prefix.endsWith(".")) prefix += ".";
        return prefix;
    }

    private static volatile CacheClient cacheClient;
    private static ApplicationContext applicationContext;
    @Resource
    JDiyProperties jDiyProperties;
    @Resource
    private SqlDao sqlDao;
    private static final Map<String, Dao> daoMap = new ConcurrentHashMap<>();/* Lazy load. */
}
